home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / ISSUE17 / CONSTRUC / RULEBASE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-12-09  |  16.4 KB  |  634 lines

  1. unit RULEBASE;
  2. {$IFDEF WIN32}
  3.   {$R RULEBASE.D32}
  4. {$ELSE}
  5.   {$R RULEBASE.D16}
  6. {$ENDIF}
  7. interface
  8. uses
  9.   Classes, Forms, ExtCtrls, DB, DBTables, DBCtrls, Grids, DBGrids, SysUtils;
  10.  
  11. Type
  12.   TBaseForm = class(TForm)
  13.     Table1: TTable;
  14.     DataSource1: TDataSource;
  15.     Panel1: TPanel;
  16.     DBNavigator1: TDBNavigator;
  17.     DBGrid1: TDBGrid;
  18.     procedure FormShow(Sender: TObject);
  19.   end;
  20.  
  21. {$IFNDEF WIN32}
  22. Type
  23.   ShortString = String;
  24. {$ENDIF}
  25.  
  26. Const
  27.   MaxFact = High(Byte);
  28.   MaxRule = High(Byte);
  29.  
  30. Type
  31.   TName32 = String[32];
  32.   TValue = ShortString;
  33.  
  34. Type
  35.   TFact = class(TObject)
  36.   private
  37.     FFact: Integer;
  38.     FGoal: Boolean;
  39.     FName: TName32;
  40.     FValue: TValue;
  41.     FQuestion: ShortString;
  42.   protected
  43.     constructor Create(Table: TTable); virtual;
  44.   public
  45.     property Fact: Integer read FFact;
  46.     property Goal: Boolean read FGoal;
  47.     property Name: TName32 read FName;
  48.     property Value: TValue read FValue write FValue;
  49.     property Question: ShortString read FQuestion;
  50.   end {TFact};
  51.  
  52.   TFactBase = class(TComponent)
  53.   private
  54.     FActive: Boolean;
  55.     FFactBase: TFileName;
  56.     FNumFact: Integer;
  57.   protected
  58.     FactTable: TTable;
  59.     Facts: Array[0..MaxFact] of TFact;
  60.   protected
  61.     procedure SetFactBase(NewFactBase: TFileName);
  62.     procedure SetActive(NewActive: Boolean);
  63.   public
  64.     constructor Create(AOwner: TComponent); override;
  65.     destructor Destroy; override;
  66.   public
  67.     procedure Open; virtual;
  68.     procedure Close; virtual;
  69.   public
  70.     procedure NewFactBase;
  71.     procedure Reset;
  72.   published
  73.     property Active: Boolean read FActive write SetActive;
  74.     property FactBase: TFileName read FFactBase write SetFactBase;
  75.     property NumFact: Integer read FNumFact;
  76.   end {TFactBase};
  77.  
  78. Type
  79.   TRule = class(TObject)
  80.   private
  81.     FRule: Integer;
  82.     FCF:  SmallInt;
  83.     FFact: Integer;
  84.     FValue: TValue;
  85.     FComments: ShortString;
  86.   protected
  87.     FFired: Boolean;
  88.     constructor Create(Table: TTable); virtual;
  89.   public
  90.     property Rule: Integer read FRule;
  91.     property CF:  SmallInt read FCF;
  92.     property Fact: Integer read FFact;
  93.     property Value: TValue read FValue;
  94.     property Fired: Boolean read FFired write FFired;
  95.     property Comments: ShortString read FComments;
  96.   end {TRule};
  97.  
  98.   TRuleBase = class(TComponent)
  99.   private
  100.     FActive: Boolean;
  101.     FRuleBase: TFileName;
  102.     FFactBase: TFactBase;
  103.     FNumRule: Integer;
  104.   protected
  105.     RuleMax: Integer;
  106.     RuleTable: TTable;
  107.     Rules: Array[0..MaxRule] of TRule;
  108.   protected
  109.     procedure SetFactBase(NewFactBase: TFactBase);
  110.     procedure SetRuleBase(NewRuleBase: TFileName);
  111.     procedure SetActive(NewActive: Boolean);
  112.   protected
  113.     function TestRule(RuleNr: Integer): Boolean;
  114.     procedure FireRule(RuleNr: Integer);
  115.     function Conclude(RuleNr, FactNr: Integer): Boolean;
  116.   public
  117.     constructor Create(AOwner: TComponent); override;
  118.     destructor Destroy; override;
  119.   public
  120.     procedure Open; virtual;
  121.     procedure Close; virtual;
  122.   public
  123.     procedure NewRuleBase;
  124.     procedure Reset;
  125.   public
  126.     function Forwards: Integer;
  127.     procedure Backwards(Goal: Integer);
  128.   published
  129.     property Active: Boolean read FActive write SetActive;
  130.     property NumRule: Integer read FNumRule;
  131.     property RuleBase: TFileName read FRuleBase write SetRuleBase;
  132.     property FactBase: TFactBase read FFactBase write SetFactBase;
  133.   end {TRuleBase};
  134.  
  135.   procedure Register;
  136.  
  137. implementation
  138. {$R *.DFM}
  139. uses
  140.   Controls, Dialogs, DsgnIntf;
  141.  
  142. procedure TBaseForm.FormShow(Sender: TObject);
  143. begin
  144.   Table1.Open
  145. end;
  146.  
  147. { TFact }
  148.  
  149.   constructor TFact.Create(Table: TTable);
  150.   begin
  151.     inherited Create;
  152.     with Table do
  153.     begin
  154.       FFact := FieldByName('Fact').AsInteger;
  155.       FGoal := FieldByName('Goal').AsBoolean;
  156.       FName := FieldByName('Name').AsString;
  157.       FValue := 'unknown';
  158.       FQuestion := FieldByName('Question').AsString
  159.     end
  160.   end {Create};
  161.  
  162. { TFactBase }
  163.  
  164.   constructor TFactBase.Create(AOwner: TComponent);
  165.   begin
  166.     inherited Create(AOwner);
  167.     FactTable := TTable.Create(Self)
  168.   end {Create};
  169.  
  170.   destructor TFactBase.Destroy;
  171.   begin
  172.     Close;
  173.     FactTable.Free;
  174.     FactTable := nil;
  175.     inherited Destroy
  176.   end {Destroy};
  177.  
  178.   procedure TFactBase.SetFactBase(NewFactBase: TFileName);
  179.   begin
  180.     if NewFactBase <> FFactBase then
  181.     begin
  182.       Close;
  183.       FactTable.DataBaseName := ExtractFilePath(NewFactBase);
  184.       FactTable.TableName := ExtractFileName(NewFactBase);
  185.       FFactBase := NewFactBase
  186.     end
  187.   end {SetFactBase};
  188.  
  189.   procedure TFactBase.SetActive(NewActive: Boolean);
  190.   var i: Integer;
  191.   begin
  192.     if not (csReading in ComponentState) then { skip loading }
  193.     if NewActive <> FActive then
  194.     begin
  195.       if NewActive then
  196.       begin
  197.         FactTable.Open;
  198.         FactTable.First;
  199.         while not FactTable.Eof do
  200.         begin
  201.           if FactTable.FieldByName('Fact').AsInteger <> FNumFact then
  202.             raise Exception.Create('Error: facts are not sorted...');
  203.           Facts[FNumFact] := TFact.Create(FactTable);
  204.           FactTable.Next;
  205.           Inc(FNumFact)
  206.         end;
  207.         FActive := True
  208.       end
  209.       else { Close }
  210.       begin
  211.         FactTable.Close;
  212.         for i:=0 to Pred(FNumFact) do
  213.         begin
  214.           Facts[i].Free;
  215.           Facts[i] := nil
  216.         end;
  217.         FNumFact := 0;
  218.         FActive := False
  219.       end
  220.     end
  221.   end {SetActive};
  222.  
  223.   procedure TFactBase.Open;
  224.   begin
  225.     Active := True
  226.   end {Open};
  227.  
  228.   procedure TFactBase.Close;
  229.   begin
  230.     Active := False
  231.   end {Close};
  232.  
  233.   procedure TFactBase.NewFactBase;
  234.   begin
  235.     with FactTable do
  236.     begin
  237.       Active := False;
  238.       TableType := ttParadox;
  239.       TableName := FFactBase;
  240.       with FieldDefs do
  241.       begin
  242.         Clear;
  243.         Add('Fact', ftInteger, 0, TRUE);
  244.         Add('Goal', ftBoolean, 0, TRUE);
  245.         Add('Name', ftString, 32, TRUE);
  246.         Add('Question', ftString, 255, FALSE)
  247.       end;
  248.       with IndexDefs do
  249.       begin
  250.         Clear;
  251.         Add('index', 'Fact', [ixPrimary,ixUnique])
  252.       end;
  253.       CreateTable
  254.     end
  255.   end {CreateFACTS};
  256.  
  257.   procedure TFactBase.Reset;
  258.   var i: Integer;
  259.   begin
  260.     for i:=0 to MaxFact do
  261.       if Facts[i] <> nil then Facts[i].Value := 'unknown'
  262.   end {Reset};
  263.  
  264. { TRule }
  265.  
  266.   constructor TRule.Create(Table: TTable);
  267.   begin
  268.     inherited Create;
  269.     with Table do
  270.     begin
  271.       FRule := FieldByName('Rule').AsInteger;
  272.       FCF := FieldByName('CF').AsInteger;
  273.       FFact := FieldByName('Fact').AsInteger;
  274.       FValue := FieldByName('Value').AsString;
  275.       FComments := FieldByName('Comments').AsString
  276.     end
  277.   end {Create};
  278.  
  279. { TRuleBase }
  280.  
  281.   constructor TRuleBase.Create(AOwner: TComponent);
  282.   begin
  283.     inherited Create(AOwner);
  284.     RuleTable := TTable.Create(Self)
  285.   end {Create};
  286.  
  287.   destructor TRuleBase.Destroy;
  288.   begin
  289.     Close;
  290.     RuleTable.Free;
  291.     RuleTable := nil;
  292.     inherited Destroy
  293.   end {Destroy};
  294.  
  295.   procedure TRuleBase.SetFactBase(NewFactBase: TFactBase);
  296.   begin
  297.     FFactBase := NewFactBase
  298.   end {SetFactBase};
  299.  
  300.   procedure TRuleBase.SetRuleBase(NewRuleBase: TFileName);
  301.   begin
  302.     if NewRuleBase <> FRuleBase then
  303.     begin
  304.       Close;
  305.       RuleTable.DataBaseName := ExtractFilePath(NewRuleBase);
  306.       RuleTable.TableName := ExtractFileName(NewRuleBase);
  307.       FRuleBase := NewRuleBase
  308.     end
  309.   end {SetRuleBase};
  310.  
  311.   procedure TRuleBase.SetActive(NewActive: Boolean);
  312.   var i: Integer;
  313.   begin
  314.     if not (csReading in ComponentState) then { skip loading }
  315.     if NewActive <> FActive then
  316.     begin
  317.       if NewActive then
  318.       begin
  319.         RuleTable.Open;
  320.         RuleTable.First;
  321.         while not RuleTable.Eof do
  322.         begin
  323.           Rules[FNumRule] := TRule.Create(RuleTable);
  324.           if Rules[FNumRule].Rule > RuleMax then
  325.             RuleMax := Rules[FNumRule].Rule;
  326.           RuleTable.Next;
  327.           Inc(FNumRule)
  328.         end;
  329.         FActive := True
  330.       end
  331.       else { Close }
  332.       begin
  333.         RuleTable.Close;
  334.         for i:=0 to MaxRule do
  335.         begin
  336.           Rules[i].Free;
  337.           Rules[i] := nil
  338.         end;
  339.         FNumRule := 0;
  340.         FActive := False
  341.       end
  342.     end
  343.   end {SetActive};
  344.  
  345.   procedure TRuleBase.Open;
  346.   begin
  347.     Active := True
  348.   end {Open};
  349.  
  350.   procedure TRuleBase.Close;
  351.   begin
  352.     Active := False
  353.   end {Close};
  354.  
  355.   procedure TRuleBase.NewRuleBase;
  356.   begin
  357.     with RuleTable do
  358.     begin
  359.       Active := False;
  360.       TableType := ttParadox;
  361.       TableName := FRuleBase;
  362.       with FieldDefs do
  363.       begin
  364.         Clear;
  365.         Add('Rule', ftInteger, 0, TRUE);
  366.         Add('CF',  ftSmallInt, 0, TRUE);
  367.         Add('Fact', ftInteger, 0, TRUE);
  368.         Add('Value', ftString, 255, FALSE);
  369.         Add('Comments', ftString, 255, FALSE)
  370.       end;
  371.       with IndexDefs do
  372.       begin
  373.         Clear;
  374.         Add('index', 'Rule;CF;Fact', [ixPrimary,ixUnique])
  375.       end;
  376.       CreateTable
  377.     end
  378.   end {CreateRULES};
  379.  
  380.   procedure TRuleBase.Reset;
  381.   var i: Integer;
  382.   begin
  383.     if FFactBase <> nil then FFactBase.Reset;
  384.     for i:=0 to MaxRule do
  385.       if Rules[i] <> nil then Rules[i].Fired := False
  386.   end {Reset};
  387.  
  388.   function TRuleBase.TestRule(RuleNr: Integer): Boolean;
  389.   var i: Integer;
  390.   begin
  391.     Result := True;
  392.     for i:=0 to Pred(FNumRule) do
  393.       if (Rules[i].Rule = RuleNr) and (Rules[i].CF = 0) then { check }
  394.         Result := Result AND
  395.           (FFactBase.Facts[Rules[i].Fact].Value = Rules[i].Value)
  396.          { NOTE: we need to compare two strings case-insensitive here... }
  397.   end {TestRule};
  398.  
  399.   procedure TRuleBase.FireRule(RuleNr: Integer);
  400.   var i: Integer;
  401.   begin
  402.     for i:=0 to Pred(FNumRule) do
  403.       if (Rules[i].Rule = RuleNr) and
  404.          (Rules[i].CF > 0) and not Rules[i].Fired then { fire }
  405.       begin
  406.         FFactBase.Facts[Rules[i].Fact].Value := Rules[i].Value;
  407.         Rules[i].FFired := True
  408.       end
  409.   end {FireRule};
  410.  
  411.   function TRuleBase.Conclude(RuleNr, FactNr: Integer): Boolean;
  412.   var i: Integer;
  413.   begin
  414.     Result := False;
  415.     for i:=0 to Pred(FNumRule) do
  416.       if (Rules[i].Rule = RuleNr) and
  417.          (Rules[i].Fact = FactNr) and
  418.          (Rules[i].CF > 0) then Result := True { rule can be used }
  419.   end {Conclude};
  420.  
  421.   function TRuleBase.Forwards: Integer;
  422.   var
  423.     RulesFired,i: Integer;
  424.   begin
  425.     if (FFactBase = nil) then raise Exception.Create('no FactBase');
  426.     if not FFactBase.Active then raise Exception.Create('FactBase not open');
  427.     if not Active then raise Exception.Create('RuleBase not open');
  428.     Result := 0;
  429.     RulesFired := NumRule;
  430.     while (Result = 0) and (RulesFired > 0) do
  431.     begin
  432.       RulesFired := 0;
  433.       for i:=0 to RuleMax do { all rules }
  434.       begin
  435.         if TestRule(i) then
  436.         begin
  437.           FireRule(i);
  438.           Inc(RulesFired)
  439.         end
  440.       end;
  441.       Result := FFactBase.NumFact;
  442.       while (Result > 0) and
  443.             ((not FFactBase.Facts[Result].Goal) or
  444.              ((FFactBase.Facts[Result].Goal) and
  445.               (FFactBase.Facts[Result].Value = 'unknown'))) do Dec(Result)
  446.     end
  447.   end {Forwards};
  448.  
  449.   procedure TRuleBase.Backwards(Goal: Integer);
  450.   Const Depth: Word = 0;
  451.   var i,j: Integer;
  452.   begin
  453.     if (FFactBase = nil) then raise Exception.Create('no FactBase');
  454.     if not FFactBase.Active then raise Exception.Create('FactBase not open');
  455.     if not Active then raise Exception.Create('RuleBase not open');
  456.     Inc(Depth);
  457.     writeln(' ':Depth,Goal);
  458.     i := 0;
  459.     while i <= RuleMax do { all rules }
  460.     begin
  461.       if Conclude(i,Goal) then
  462.       begin
  463.         if TestRule(i) then FireRule(i)
  464.         else { infer or ask }
  465.         begin
  466.           j := 0;
  467.           while j < NumRule do
  468.           begin
  469.             if (Rules[j].Rule = i) and (Rules[j].CF = 0) and
  470.                (FFactBase.Facts[Rules[j].Fact].Value = 'unknown') then
  471.             begin
  472.               Backwards(Rules[j].Fact); { infer }
  473.               if TestRule(i) then j := NumRule
  474.               else { ask }
  475.               begin
  476.                 if FFactBase.Facts[Rules[j].Fact].Question <> '' then
  477.                 begin
  478.                   writeln(' ':Depth,FFactBase.Facts[Rules[j].Fact].Question);
  479.                   if MessageDlg(FFactBase.Facts[Rules[j].Fact].Question,
  480.                                  mtConfirmation,[mbYes,mbNo],0) = mrYes then
  481.                     FFactBase.Facts[Rules[j].Fact].Value := 'Yes'
  482.                   else
  483.                     FFactBase.Facts[Rules[j].Fact].Value := 'No'
  484.                 end;
  485.                 if TestRule(i) then j := NumRule
  486.               end
  487.             end;
  488.             Inc(j)
  489.           end;
  490.           if TestRule(i) then
  491.           begin
  492.             FireRule(i);
  493.             i := RuleMax
  494.           end
  495.         end
  496.       end;
  497.       Inc(i)
  498.     end;
  499.     Dec(Depth);
  500.     if Depth = 0 then { final goal proven? }
  501.     begin
  502.       writeln;
  503.       writeln(FFactBase.Facts[Goal].Name,': ',
  504.               FFactBase.Facts[Goal].Value);
  505.       ShowMessage(FFactBase.Facts[Goal].Name + #13 +
  506.                   FFactBase.Facts[Goal].Value)
  507.     end
  508.   end {Backwards};
  509.  
  510. { TFileNameProperty }
  511.  
  512. Type
  513.   TFileNameProperty = class(TStringProperty)
  514.   public
  515.     function GetAttributes: TPropertyAttributes; override;
  516.     procedure Edit; override;
  517.   end;
  518.  
  519.   function TFileNameProperty.GetAttributes: TPropertyAttributes;
  520.   begin
  521.     Result := [paDialog]
  522.   end {GetAttributes};
  523.  
  524.   procedure TFileNameProperty.Edit;
  525.   begin
  526.     with TOpenDialog.Create(nil) do
  527.     try
  528.       Title := GetName; { name of property as OpenDialog caption }
  529.       Filename := GetValue;
  530.       Filter := 'DB Files (*.DB)|*.DB';
  531.       HelpContext := 0;
  532.       Options := Options + [ofShowHelp, ofPathMustExist, ofFileMustExist];
  533.       if Execute then SetValue(Filename)
  534.     finally
  535.       Free
  536.     end
  537.   end {Edit};
  538.  
  539. { TFactBaseProperty }
  540.  
  541. Type
  542.   TFactBaseProperty = class(TComponentProperty)
  543.   public
  544.     function GetAttributes: TPropertyAttributes; override;
  545.     procedure GetValues(Proc: TGetStrProc); override;
  546.   end;
  547.  
  548.   function TFactBaseProperty.GetAttributes: TPropertyAttributes;
  549.   begin
  550.     Result := [paValueList]
  551.   end {GetAttributes};
  552.  
  553. (*
  554.   procedure TFactBaseProperty.GetValues(Proc: TGetStrProc);
  555.   var i: Integer;
  556.       Component: TComponent;
  557.   begin
  558.     for i:=0 to Pred(Designer.Form.ComponentCount) do
  559.     begin
  560.       Component := Designer.Form.Components[i];
  561.       if (Component IS TFactBase) and (Component.Name <> '') then
  562.         Proc(Component.Name)
  563.     end
  564.   end {GetValues};
  565. *)
  566.  
  567.   procedure TFactBaseProperty.GetValues(Proc: TGetStrProc);
  568.   var i: Integer;
  569.   begin
  570.     with Designer.Form do
  571.     begin
  572.       for i:=0 to Pred(ComponentCount) do
  573.       begin
  574.         if (Components[i] IS TFactBase) and (Components[i].Name <> '') then
  575.           Proc(Components[i].Name)
  576.       end
  577.     end
  578.   end {GetValues};
  579.  
  580. { TFactBaseComponentEditor }
  581.  
  582. Type
  583.   TFactBaseComponentEditor = class(TComponentEditor)
  584.   public
  585.     procedure Edit; override;
  586.   end;
  587.  
  588.   procedure TFactBaseComponentEditor.Edit;
  589.   begin
  590.     with TBaseForm.Create(nil) do
  591.     try
  592.       Caption := 'FactBase '+(Component AS TFactBase).FactBase;
  593.       Table1.DataBaseName := (Component AS TFactBase).FactTable.DataBaseName;
  594.       Table1.TableName := (Component AS TFactBase).FactTable.TableName;
  595.       ShowModal
  596.     finally
  597.       Free
  598.     end
  599.   end {Edit};
  600.  
  601. { TRuleBaseComponentEditor }
  602.  
  603. Type
  604.   TRuleBaseComponentEditor = class(TComponentEditor)
  605.   public
  606.     procedure Edit; override;
  607.   end;
  608.  
  609.   procedure TRuleBaseComponentEditor.Edit;
  610.   begin
  611.     with TBaseForm.Create(nil) do
  612.     try
  613.       Caption := 'RuleBase '+(Component AS TRuleBase).RuleBase;
  614.       Table1.DataBaseName := (Component AS TRuleBase).RuleTable.DataBaseName;
  615.       Table1.TableName := (Component AS TRuleBase).RuleTable.TableName;
  616.       ShowModal
  617.     finally
  618.       Free
  619.     end
  620.   end {Edit};
  621.  
  622. { register }
  623.  
  624.   procedure Register;
  625.   begin
  626.     RegisterComponents('Dr.Bob', [TFactBase, TRuleBase]);
  627.     RegisterPropertyEditor(TypeInfo(TFileName), TFactBase, 'FactBase', TFileNameProperty);
  628.     RegisterPropertyEditor(TypeInfo(TFileName), TRuleBase, 'RuleBase', TFileNameProperty);
  629.     RegisterPropertyEditor(TypeInfo(TFactBase), TRuleBase, 'FactBase', TFactBaseProperty);
  630.     RegisterComponentEditor(TFactBase, TFactBaseComponentEditor);
  631.     RegisterComponentEditor(TRuleBase, TRuleBaseComponentEditor)
  632.   end;
  633. end.
  634.